package uk.ac.rothamsted;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import uk.ac.rothamsted.utils.ArrayKey;
import uk.ac.rothamsted.utils.OrthologCluster;
import uk.ac.rothamsted.utils.OrthologClusterSet;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ListMultimap;

public class InParanoid{

	private int scoreCutoff;
	private int overlapCutoff;



	public InParanoid(int setScoreCutoff, int setOverlapCutoff){
		scoreCutoff = setScoreCutoff;
		overlapCutoff = setOverlapCutoff;
	}


	public Map<ArrayKey<String>, BlastData> readfile(String file) throws IOException{
		Map<ArrayKey<String>, BlastData> result = new HashMap<ArrayKey<String>, BlastData>();

		BufferedReader br = new BufferedReader(new FileReader(file));
		String line;
		double score;
		double data3;
		double data4;
		double data5;
		double data6;
		double overlap;
		double overlapA;
		double overlapB;

		int loopNum = 0;
		while((line = br.readLine()) != null){

			// Split line on tab
			String data [] = line.split("\t");

			score = Double.parseDouble(data[2]);
			data3 = Double.parseDouble(data[3]);
			data4 = Double.parseDouble(data[4]);
			data5 = Double.parseDouble(data[5]);
			data6 = Double.parseDouble(data[6]);

			overlapA = (data5/data3)*100;	//calculate overlap values
			overlapB = (data6/data4)*100;

			if(overlapA <= overlapB){	//use lowest overlap value
				overlap = overlapA;
			}else{
				overlap = overlapB;
			}

			String keys [] = {data[0], data[1]};

			//if it has score and overlap greater than their respective cutoff values add to output.
			if((score>=scoreCutoff)&&(overlap>=overlapCutoff)){		
				BlastData inputLine = new BlastData(score, overlap, data[0], data[1], loopNum);
				result.put(new ArrayKey<String>(keys), inputLine);
			}
		}
		br.close();
		return result;
	}




	//using the original fasts files for each species, create a list of all genes, and id numbers for each.
	//return a list of objects the first is a set with all gene names for that species,
	//the second is a bi directional map for species name to ID.
	public List<Object> listFastaSpecies(String file) throws IOException{
		List<Object> output = new ArrayList<Object>();
		Set<String> outputSet= new HashSet<String>();
		BiMap<Integer, String> speciesMap = HashBiMap.create();


		BufferedReader br = new BufferedReader(new FileReader(file));
		String line;

		int x=0;

		while((line = br.readLine()) != null){
			if(Character.toString(line.charAt(0)).equals(">")){
				String name = line.substring(1);
				name.trim();
				if(!outputSet.contains(name)){
					outputSet.add(name);
					speciesMap.put(x, name);
				}	
			}
			x++;
		}

		output.add(0, outputSet);
		output.add(1, speciesMap);
		return output;
	}






	public Map<ArrayKey<String>, BlastData> averageSequences(Map<ArrayKey<String>, BlastData> input, Set<String> speciesList, BiMap<Integer, String> speciesMapA, BiMap<Integer, String> speciesMapB){

		Map<ArrayKey<String>, BlastData> output = new HashMap<ArrayKey<String>, BlastData>(); 
		for(Entry<ArrayKey<String>, BlastData> ent : input.entrySet()){ //iterate through input data.

			String keyNames [] = ent.getKey().getArray(); // get two sequence names.
			String keyNamesInv [] = {keyNames[1], keyNames[0]}; //invert the order of the sequence names.
			ArrayKey<String> key = ent.getKey(); 
			ArrayKey<String> keyInv = new ArrayKey<String>(keyNamesInv);


			if( !( (output.containsKey(key)) || (output.containsKey(keyInv)) ) ){ 
				// continue only if the sequence pair in either direction, does not exist in the output.
				if(input.containsKey(keyInv)){          //check there is a corresponding blast in the other direction in the input.

					BlastData data1 = ent.getValue();       //get the two blast data, one for either direction.
					BlastData data2 = input.get(keyInv);    

					double averageScore;
					double averageOverlap;

					if(data1.getScore() == data2.getScore()){
						averageScore = data1.getScore();
					} else {
						averageScore = data1.getScore() + data2.getScore(); //find the average for the Score.
						averageScore = averageScore / 2.0;
					}

					averageScore = Math.round(averageScore);


					if(data1.getOverlap() == data2.getOverlap()){
						averageOverlap = data1.getOverlap();
					} else {
						averageOverlap = data1.getOverlap() + data2.getOverlap();
						averageOverlap = averageOverlap / 2.0;
					}


					if(speciesList.contains(keyNames[0])){                                          //organise the key so that speciesA is first.
						int idNumTotal = speciesMapA.inverse().get(keyNames[0]) + speciesMapB.inverse().get(keyNames[1]);    
						output.put(key, new BlastData(averageScore, averageOverlap, keyNames[0], keyNames[1], idNumTotal));
					}else{
						int idNumTotal = speciesMapA.inverse().get(keyNames[1]) + speciesMapB.inverse().get(keyNames[0]);
						output.put(keyInv, new BlastData(averageScore, averageOverlap, keyNames[1], keyNames[0], idNumTotal));
					}
				}
			}
		}
		return output;
	}




	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Set< BlastData> bestHit(Map<ArrayKey<String>, BlastData> input,  Set<String> subjectSpecies, Set<String> querySpecies ){
		Set<BlastData> output = new HashSet< BlastData>();
		Set<BlastData> outputSubQuery = new HashSet< BlastData>();
		Set<BlastData> outputQuerySub = new HashSet< BlastData>();
		// Storing the ids of the genes for the hit in the BlastData class will
		// make this dataset much less of a pain to work with...


		//Sort all the hits for subject species the in a list map - subject species id as
		// key, list of all BlastData for it as value
		ListMultimap<String, Entry<ArrayKey<String>, BlastData>> lmSubQuery =  ArrayListMultimap.create();
		ListMultimap<String, Entry<ArrayKey<String>, BlastData>> lmQuerySub =  ArrayListMultimap.create();
		for(Entry<ArrayKey<String>, BlastData> ent : input.entrySet()){
			String idSubject = ent.getKey().getArray()[0];
			String idQuery = ent.getKey().getArray()[1];
			if(idSubject.equals("SPCC285.13c")){
				System.out.println(ent);
			}
			if(subjectSpecies.contains(idSubject)){
				//if(ent.getValue().getPairwise()){
				lmSubQuery.put(idSubject, ent);
				//}
			}
			if(querySpecies.contains(idQuery)){
				lmQuerySub.put(idQuery, ent);

			}

		}





		//Comparator that can work on the these entries 
		Comparator<Entry<ArrayKey<String>, BlastData>> comp = new Comparator<Entry<ArrayKey<String>, BlastData>>(){
			@Override
			public int compare(Entry<ArrayKey<String>, BlastData> o1,Entry<ArrayKey<String>, BlastData> o2) {
				if(o1.getValue().getScore() == o2.getValue().getScore()){
					return 0;
				}else if(o1.getValue().getScore() < o2.getValue().getScore()){
					return -1;
				}
				return 1;
			}
		};

		//Iterate over the ListMap and find the best hit from subject to query using the comparator

		for(Entry<String, Collection<Entry<ArrayKey<String>, BlastData>>> ent : lmSubQuery.asMap().entrySet()){
			Entry<ArrayKey<String>, BlastData> bestSubQuery = Collections.max(ent.getValue(), comp);
			Collections.sort((List) ent.getValue(), comp);
			Collections.reverse((List)ent.getValue());

			for(Entry<ArrayKey<String>, BlastData> tempBest : ent.getValue()){
				if( tempBest.getValue().getScore()==bestSubQuery.getValue().getScore()){
					outputSubQuery.add(tempBest.getValue());
				}else{
					break;
				}
			}
		}


		//Iterate over the ListMap and find the best hit using the comparator this time gauge best hits for query to subject
		for(Entry<String, Collection<Entry<ArrayKey<String>, BlastData>>> ent : lmQuerySub.asMap().entrySet()){
			Entry<ArrayKey<String>, BlastData> bestQuerySub = Collections.max(ent.getValue(), comp);
			Collections.sort((List) ent.getValue(), comp);
			Collections.reverse((List)ent.getValue());

			for(Entry<ArrayKey<String>, BlastData> tempBest : ent.getValue()){
				if( tempBest.getValue().getScore()==bestQuerySub.getValue().getScore()){
					outputQuerySub.add(tempBest.getValue());
				}else{
					break;
				}
			}
		}


		//compare the best hits from subject to query and query to subject and where they match add to output
		for(BlastData iterSubQuery : outputSubQuery){
			for(BlastData iterQuerySub : outputQuerySub){
				if(iterSubQuery.equals(iterQuerySub)){
					//if((iterSubQuery.getScore()>=scoreCutoff)&&(iterSubQuery.getOverlap()>=overlapCutoff))
						output.add(iterSubQuery);	//Add to output only if its the best hits in both directions
				}
			}
		}
		return output;
	}




	//test for overlap 1, if found merge overlapCluster into cluster
	public boolean overlap1(OrthologCluster cluster, OrthologCluster overlapCluster){
		if(overlapCluster.isInvalid()||cluster.isInvalid()){
			return false;
		}
		boolean clusterInOverlap = (cluster.contains(overlapCluster.getMainPair().id1))&&(cluster.contains(overlapCluster.getMainPair().id2));
		boolean OverlapInCluster = (overlapCluster.contains(cluster.getMainPair().id1))&&(overlapCluster.contains(cluster.getMainPair().id2));
		if(clusterInOverlap && OverlapInCluster){
			cluster.merge(overlapCluster);
			return true;
		}
		return false;
	}


	//test for overlap 2 if found merge overlapCluster into cluster
	public boolean overlap2(OrthologCluster cluster, OrthologCluster overlapCluster){
		if(overlapCluster.isInvalid()||cluster.isInvalid()){
			return false;
		}
		if(cluster.getScore() == overlapCluster.getScore()){
			cluster.merge(overlapCluster);
			return true;
		}
		return false;
	}


	//test for overlap 3 if found delete overlapCluster
	public boolean overlap3(OrthologCluster cluster, OrthologCluster overlapCluster){
		if(overlapCluster.isInvalid()||cluster.isInvalid()){
			return false;
		}
		if((overlapCluster.getScore() - cluster.getScore())>50){
			overlapCluster.getParentSet().deleteCluster(overlapCluster);
			return true;
		}
		return false;
	}


	//test for overlap 4 if found merge overlapCluster into cluster
	public boolean overlap4(OrthologCluster cluster, OrthologCluster overlapCluster){
		if(overlapCluster.isInvalid()||cluster.isInvalid()){
			return false;
		}
		String seed1B = cluster.getMainPair().id2;
		String seed1A = cluster.getMainPair().id1;

		String seed2A = overlapCluster.getMainPair().id1;
		String seed2B = overlapCluster.getMainPair().id2;

		if(cluster.contains(seed2A)&& overlapCluster.contains(seed1A)){
			ArrayKey<String> key1A2A = cluster.getParentSet().getSortedKey(seed1A,seed2A);
			ArrayKey<String> key1A1B = cluster.getParentSet().getSortedKey(seed1A,seed1B);
			double score1A2A = cluster.getParentSet().getAllHits().get(key1A2A).score;
			double score1A1B = cluster.getParentSet().getAllHits().get(key1A1B).score;

			if(score1A2A > (0.5*score1A1B)){
				cluster.merge(overlapCluster);
				return true;
			}
		}else if(cluster.contains(seed2B) && overlapCluster.contains(seed1B)){
			ArrayKey<String> key1B2B = cluster.getParentSet().getSortedKey(seed1B,seed2B);
			ArrayKey<String> key1B1A = cluster.getParentSet().getSortedKey(seed1B,seed1A);
			double score1B2B = cluster.getParentSet().getAllHits().get(key1B2B).score;
			double score1B1A = cluster.getParentSet().getAllHits().get(key1B1A).score;
			if(score1B2B > (0.5*score1B1A)){
				cluster.merge(overlapCluster);
				return true;
			}
		}
		return false;
	}



	//test for overlap 5 if found divide the clusters.
	public boolean overlap5(OrthologCluster cluster, OrthologCluster overlapCluster){
		if(overlapCluster.isInvalid()||cluster.isInvalid()){
			return false;
		}
		String seed1A = cluster.getMainPair().id1;//Cluster 1, Species A
		String seed1B = cluster.getMainPair().id2;//Cluster 1, Species B

		String seed2A = overlapCluster.getMainPair().id1;//Cluster 2, Species A
		String seed2B = overlapCluster.getMainPair().id2;//Cluster 2, Species B
		OrthologClusterSet parentSet = cluster.getParentSet();

		for(Iterator<String> itr = cluster.getMembers().iterator(); itr.hasNext();){
			String paralog = itr.next();
			ArrayKey<String> search1p;
			ArrayKey<String> search2p;
			if(cluster.getParentSet().getSpeciesId(paralog)==0){
				search1p = parentSet.getSortedKey(paralog, seed1A);
				search2p = parentSet.getSortedKey(paralog, seed2A);
			}else if(cluster.getParentSet().getSpeciesId(paralog)==1){
				search1p = parentSet.getSortedKey(paralog, seed1B);
				search2p = parentSet.getSortedKey(paralog, seed2B);
			}else{
				return false;
			}

			if(overlapCluster.contains(paralog)){
				double score1p = 0;
				double score2p = 0;

				if(parentSet.getAllHits().containsKey(search1p)){
					score1p = (parentSet.getAllHits().get(search1p)).score;
				}

				if(parentSet.getAllHits().containsKey(search2p)){
					score2p = (parentSet.getAllHits().get(search2p)).score;
				}

				if(score1p > score2p){
					overlapCluster.remove(paralog);
				}else{
					itr.remove();
				}

			}
		}
		return true;
	}



	//retrieve all overlapping cluster for provided cluster and apply merge rules.
	public void merge(OrthologCluster cluster1){
		if(cluster1.isInvalid()){
			return;
		}

		//for each overlapping cluster find the stronger cluster

		for(OrthologCluster cluster2 : cluster1.getOverlappingClusters()){
			OrthologCluster greaterCluster;
			OrthologCluster lesserCluster;
			if(cluster1.getScore() > cluster2.getScore()){
				greaterCluster = cluster1;
				lesserCluster = cluster2;
			}else{
				greaterCluster = cluster2;
				lesserCluster = cluster1;
			}

			BlastData lesserClusterMainPair = lesserCluster.getMainPair();

			if(overlap1(greaterCluster,	lesserCluster)){
				System.out.println("1 " + greaterCluster.getMainPair().id1 + "   " + greaterCluster.getMainPair().id2 + "   " + lesserClusterMainPair.id1 + "   " + lesserClusterMainPair.id2 + "   " + greaterCluster.getScore() + "   " + lesserClusterMainPair.getScore());
				continue;
			}else if(overlap2(greaterCluster,	lesserCluster)){
				System.out.println("2 " + greaterCluster.getMainPair().id1 + "   " + greaterCluster.getMainPair().id2 + "   " + lesserClusterMainPair.id1 + "   " + lesserClusterMainPair.id2);
				continue;
			}else if(overlap3(greaterCluster,	lesserCluster)){
				System.out.println("3 " + greaterCluster.getMainPair().id1 + "   " + greaterCluster.getMainPair().id2 + "   " + lesserClusterMainPair.id1 + "   " + lesserClusterMainPair.id2);
				continue;
			}else if(overlap4(greaterCluster,	lesserCluster)){
				System.out.println("4 " + greaterCluster.getMainPair().id1 + "   " + greaterCluster.getMainPair().id2 + "   " + lesserClusterMainPair.id1 + "   " + lesserClusterMainPair.id2);
				continue;
			}else if(overlap5(greaterCluster,	lesserCluster)){
				System.out.println("5 " + greaterCluster.getMainPair().id1 + "   " + greaterCluster.getMainPair().id2 + "   " + lesserClusterMainPair.id1 + "   " + lesserClusterMainPair.id2);
				continue;
			}
		}
	}

}
